/*!
 * FilePondPluginImageEdit 1.5.0
 * Licensed under MIT, https://opensource.org/licenses/MIT/
 * Please visit https://pqina.nl/filepond/ for details.
 */

/* eslint-disable */

const isPreviewableImage = file => /^image/.test(file.type);

/**
 * Image Edit Proxy Plugin
 */
const plugin = _ => {
  const { addFilter, utils, views } = _;
  const { Type, createRoute, createItemAPI = item => item } = utils;
  const { fileActionButton } = views;

  addFilter(
    'SHOULD_REMOVE_ON_REVERT',
    (shouldRemove, { item, query }) =>
      new Promise(resolve => {
        const { file } = item;

        // if this file is editable it shouldn't be removed immidiately even when instant uploading
        const canEdit =
          query('GET_ALLOW_IMAGE_PREVIEW') &&
          query('GET_ALLOW_IMAGE_EDIT') &&
          query('GET_IMAGE_EDIT_ALLOW_EDIT') &&
          isPreviewableImage(file);

        // if the file cannot be edited it should be removed on revert
        resolve(!canEdit);
      })
  );

  // open editor when loading a new item
  addFilter(
    'DID_LOAD_ITEM',
    (item, { query, dispatch }) =>
      new Promise((resolve, reject) => {
        // if is temp or local file
        if (item.origin > 1) {
          resolve(item);
          return;
        }

        // get file reference
        const { file } = item;
        if (
          !query('GET_ALLOW_IMAGE_PREVIEW') ||
          !query('GET_ALLOW_IMAGE_EDIT') ||
          !query('GET_IMAGE_EDIT_INSTANT_EDIT')
        ) {
          resolve(item);
          return;
        }

        // exit if this is not an image
        if (!isPreviewableImage(file)) {
          resolve(item);
          return;
        }

        const createEditorResponseHandler = (
          item,
          resolve,
          reject
        ) => userDidConfirm => {
          // remove item
          editRequestQueue.shift();

          // handle item
          if (userDidConfirm) {
            resolve(item);
          } else {
            reject(item);
          }

          // TODO: Fix, should not be needed to kick the internal loop in case no processes are running
          dispatch('KICK');

          // handle next item!
          requestEdit();
        };

        const requestEdit = () => {
          if (!editRequestQueue.length) {
            return;
          }

          const { item, resolve, reject } = editRequestQueue[0];

          dispatch('EDIT_ITEM', {
            id: item.id,
            handleEditorResponse: createEditorResponseHandler(
              item,
              resolve,
              reject
            )
          });
        };

        queueEditRequest({ item, resolve, reject });

        if (editRequestQueue.length === 1) {
          requestEdit();
        }
      })
  );

  // extend item methods
  addFilter('DID_CREATE_ITEM', (item, { query, dispatch }) => {
    item.extend('edit', () => {
      dispatch('EDIT_ITEM', { id: item.id });
    });
  });

  const editRequestQueue = [];
  const queueEditRequest = editRequest => {
    editRequestQueue.push(editRequest);
    return editRequest;
  };

  // called for each view that is created right after the 'create' method
  addFilter('CREATE_VIEW', viewAPI => {
    // get reference to created view
    const { is, view, query } = viewAPI;

    if (
      !is('file') ||
      !query('GET_ALLOW_IMAGE_PREVIEW') ||
      !query('GET_ALLOW_IMAGE_EDIT')
    ) {
      return;
    }

    // no editor defined, then exit
    const editor = query('GET_IMAGE_EDIT_EDITOR');
    if (!editor) return;

    // set default FilePond options and add bridge once
    if (!editor.filepondCallbackBridge) {
      editor.outputData = true;
      editor.outputFile = false;
      editor.cropAspectRatio =
        query('GET_IMAGE_CROP_ASPECT_RATIO') || editor.cropAspectRatio;
      editor.outputCanvasBackgroundColor =
        query('GET_IMAGE_TRANSFORM_CANVAS_BACKGROUND_COLOR') ||
        editor.outputCanvasBackgroundColor;
      editor.filepondCallbackBridge = {
        onconfirm: editor.onconfirm || (() => {}),
        oncancel: editor.oncancel || (() => {})
      };
    }

    // opens the editor, if it does not already exist, it creates the editor
    const openEditor = ({ root, props, action }) => {
      const { id } = props;
      const { handleEditorResponse } = action;

      // get item
      const item = root.query('GET_ITEM', id);
      if (!item) return;

      // file to open
      const file = item.file;

      // crop data to pass to editor
      const crop = item.getMetadata('crop');
      const cropDefault = {
        center: {
          x: 0.5,
          y: 0.5
        },
        flip: {
          horizontal: false,
          vertical: false
        },
        zoom: 1,
        rotation: 0,
        aspectRatio: null
      };

      // size data to pass to editor
      const resize = item.getMetadata('resize');

      // filter and color data to pass to editor
      const filter = item.getMetadata('filter') || null;
      const filters = item.getMetadata('filters') || null;
      const colors = item.getMetadata('colors') || null;
      const markup = item.getMetadata('markup') || null;

      // build parameters object
      const imageParameters = {
        crop: crop || cropDefault,
        size: resize
          ? {
              upscale: resize.upscale,
              mode: resize.mode,
              width: resize.size.width,
              height: resize.size.height
            }
          : null,
        filter: filters ? filters.id || filters.matrix : filter,
        color: colors,
        markup
      };

      editor.onconfirm = ({ data }) => {
        const { crop, size, filter, color, colorMatrix, markup } = data;

        // create new metadata object
        const metadata = {};

        // append crop data
        if (crop) {
          metadata.crop = crop;
        }

        // append size data
        if (size) {
          const initialSize = (item.getMetadata('resize') || {}).size;
          const targetSize = {
            width: size.width,
            height: size.height
          };

          if (!(targetSize.width && targetSize.height) && initialSize) {
            targetSize.width = initialSize.width;
            targetSize.height = initialSize.height;
          }

          if (targetSize.width || targetSize.height) {
            metadata.resize = {
              upscale: size.upscale,
              mode: size.mode,
              size: targetSize
            };
          }
        }

        if (markup) {
          metadata.markup = markup;
        }

        // set filters and colors so we can restore them when re-editing the image
        metadata.colors = color;
        metadata.filters = filter;

        // set merged color matrix to use in preview plugin
        metadata.filter = colorMatrix;

        // update crop metadata
        item.setMetadata(metadata);

        // call
        editor.filepondCallbackBridge.onconfirm(data, createItemAPI(item));

        // used in instant edit mode
        if (!handleEditorResponse) return;
        editor.onclose = () => {
          handleEditorResponse(true);
          editor.onclose = null;
        };
      };

      editor.oncancel = () => {
        // call
        editor.filepondCallbackBridge.oncancel(createItemAPI(item));

        // used in instant edit mode
        if (!handleEditorResponse) return;
        editor.onclose = () => {
          handleEditorResponse(false);
          editor.onclose = null;
        };
      };

      editor.open(file, imageParameters);
    };

    /**
     * Image Preview related
     */
    const didPreviewUpdate = ({ root }) => {
      if (!root.ref.buttonEditItem) return;
      root.ref.buttonEditItem.opacity = 1;
    };

    // create the image edit plugin, but only do so if the item is an image
    const didLoadItem = ({ root, props }) => {
      if (!query('GET_IMAGE_EDIT_ALLOW_EDIT')) {
        return;
      }

      const { id } = props;

      // try to access item
      const item = query('GET_ITEM', id);
      if (!item) return;

      // get the file object
      const file = item.file;

      // exit if this is not an image
      if (!isPreviewableImage(file)) {
        return;
      }

      // add edit button
      const buttonView = view.createChildView(fileActionButton, {
        label: 'edit',
        icon: query('GET_IMAGE_EDIT_ICON_EDIT'),
        opacity: 0
      });

      // edit item classname
      buttonView.element.classList.add('filepond--action-edit-item');
      buttonView.element.dataset.align = query(
        'GET_STYLE_IMAGE_EDIT_BUTTON_EDIT_ITEM_POSITION'
      );

      // handle interactions
      root.ref.handleEdit = e => {
        e.stopPropagation();
        root.dispatch('EDIT_ITEM', { id });
      };
      buttonView.on('click', root.ref.handleEdit);

      root.ref.buttonEditItem = view.appendChildView(buttonView);
    };

    view.registerDestroyer(({ root }) => {
      if (root.ref.buttonEditItem) {
        root.ref.buttonEditItem.off('click', root.ref.handleEdit);
      }
    });

    // start writing
    view.registerWriter(
      createRoute({
        DID_IMAGE_PREVIEW_SHOW: didPreviewUpdate,
        DID_LOAD_ITEM: didLoadItem,
        EDIT_ITEM: openEditor
      })
    );
  });

  // Expose plugin options
  return {
    options: {
      // enable or disable image editing
      allowImageEdit: [true, Type.BOOLEAN],

      // location of processing button
      styleImageEditButtonEditItemPosition: ['bottom center', Type.STRING],

      // open editor when image is dropped
      imageEditInstantEdit: [false, Type.BOOLEAN],

      // allow editing
      imageEditAllowEdit: [true, Type.BOOLEAN],

      // the icon to use for the edit button
      imageEditIconEdit: [
        '<svg width="26" height="26" viewBox="0 0 26 26" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M8.5 17h1.586l7-7L15.5 8.414l-7 7V17zm-1.707-2.707l8-8a1 1 0 0 1 1.414 0l3 3a1 1 0 0 1 0 1.414l-8 8A1 1 0 0 1 10.5 19h-3a1 1 0 0 1-1-1v-3a1 1 0 0 1 .293-.707z" fill="currentColor" fill-rule="nonzero"/></svg>',
        Type.STRING
      ],

      // editor object
      imageEditEditor: [null, Type.OBJECT]
    }
  };
};

// fire pluginloaded event if running in browser, this allows registering the plugin when using async script tags
const isBrowser =
  typeof window !== 'undefined' && typeof window.document !== 'undefined';
if (isBrowser) {
  document.dispatchEvent(
    new CustomEvent('FilePond:pluginloaded', { detail: plugin })
  );
}

export default plugin;
